﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Scripting;
using DlrBasic.Parser.Ast;

namespace DlrBasic.Parser
{
    class DlrBasicParser
    {
        DlrBasicTokenizer tokenizer;
        SourceUnit source;

        public DlrBasicParser(SourceUnit source)
        {
            this.source = source;
            this.tokenizer = new DlrBasicTokenizer(source);
            tokenizer.NextToken();
        }

        internal DlrBasic.Parser.Ast.BasicProgram ParseFile()
        {
            List<Line> lines = new List<Line>();

            try
            {
                do
                {
                    while (tokenizer.CurrentToken != null && tokenizer.CurrentToken.Kind == TokenKind.NewLine)
                    {
                        tokenizer.NextToken();
                    }
                    if (tokenizer.CurrentToken.Kind == TokenKind.EndOfFile) break;

                    lines.Add(ParseLine());
                } while (tokenizer.CurrentToken.Kind != TokenKind.EndOfFile);

                if (lines.Count == 0)
                {
                    return new BasicProgram(new SourceSpan(new SourceLocation(0, 1, 1), new SourceLocation(0, 1, 1)), new Line[0]);
                }

                BasicProgram program = new BasicProgram(
                    new SourceSpan(lines[0].SourceSpan.Start, lines[lines.Count - 1].SourceSpan.End),
                    lines.ToArray());
                return program;
            }
            catch (SyntaxErrorException)
            {
                throw;
            }
            catch (Exception ex)
            {
                SourceLocation lastLocation = lines.Count > 0 ? lines[lines.Count].SourceSpan.End : SourceLocation.None;

                throw SyntaxError(
                    String.Format("Unexpected syntax error: {0}", ex.Message),
                    new SourceSpan(lastLocation, lastLocation));
            }
        }

        private Line ParseLine()
        {
            if (tokenizer.CurrentToken.Kind == TokenKind.NumericConstant)
            {
                int lineNumber = ParseLineNumber();

                try
                {
                    Statement[] statements = ParseStatements();

                    if (tokenizer.CurrentToken.Kind == TokenKind.ShortComment)
                    {
                        SkipComment();
                    }

                    if (tokenizer.CurrentToken.Kind != TokenKind.NewLine &&
                        tokenizer.CurrentToken.Kind != TokenKind.EndOfFile)
                        throw SyntaxError(
                            String.Format(Properties.Resources.UnexpectedSymbolException, tokenizer.CurrentToken.Text),
                            tokenizer.CurrentToken.Span);

                    return new Line(new SourceSpan(statements[0].SourceSpan.Start,
                        statements[statements.Length - 1].SourceSpan.End), lineNumber, statements);
                }
                catch (Exception ex)
                {
                    throw SyntaxError(
                        String.Format("Syntax error: {0}", ex.Message),
                        tokenizer.CurrentToken.Span);
                }
            }
            else
                throw SyntaxError(Properties.Resources.LineNumberNotFoundException,
                    tokenizer.CurrentToken.Span);
        }

        private Statement[] ParseStatements()
        {
            List<Statement> statements = new List<Statement>();
            do
            {
                Statement lineStatement = ParseStatement();

                statements.Add(lineStatement);
                if (IsStatementsEnd())
                    break;

                while (tokenizer.CurrentToken.Kind == TokenKind.NewStatement)
                {
                    tokenizer.NextToken();
                }
            } while (!IsStatementsEnd());

            return statements.ToArray();
        }

        private bool IsStatementsEnd()
        {
            TokenKind tokenKind = tokenizer.CurrentToken.Kind;
            return tokenKind == TokenKind.EndOfFile || tokenKind == TokenKind.NewLine ||
                tokenizer.IsKeyword(Keywords.ElseKeywordId) || tokenizer.CurrentToken.Kind == TokenKind.ShortComment;
        }

        private Statement ParseStatement()
        {
            if (tokenizer.CurrentToken.Kind == TokenKind.Keyword)
            {
                switch (tokenizer.CurrentToken.ItemId)
                {
                    case Keywords.ClearKeywordId:
                        return ParseClear();
                    case Keywords.LetKeywordId:
                        return ParseLet();
                    case Keywords.ClsKeywordId:
                        return ParseCls();
                    case Keywords.BeepKeywordId:
                        return ParseBeep();
                    case Keywords.PrintKeywordId:
                        return ParsePrint();
                    case Keywords.InputKeywordId:
                        return ParseInput();
                    case Keywords.DimKeywordId:
                        return ParseDim();
                    case Keywords.IfKeywordId:
                        return ParseIf();
                    case Keywords.EndKeywordId:
                        return ParseEnd();
                    case Keywords.ForKeywordId:
                        return ParseFor();
                    case Keywords.NextKeywordId:
                        return ParseNext();
                    case Keywords.GosubKeywordId:
                        return ParseGosub();
                    case Keywords.GotoKeywordId:
                        return ParseGoto();
                    case Keywords.ReturnKeywordId:
                        return ParseReturn();
                    case Keywords.RemKeywordId:
                        return SkipComment();
                    case Keywords.RandomizeKeywordId:
                        return ParseRandomize();
                    case Keywords.DefKeywordId:
                        return ParseDef();
                    case Keywords.OnKeywordId:
                        return ParseOn();
                    case Keywords.LineKeywordId:
                        return ParseLineInput();
                    case Keywords.MatKeywordId:
                        return ParseMat();
                    default:
                        throw SyntaxError(
                            String.Format(Properties.Resources.UnexpectedStatementException, tokenizer.CurrentToken.Text),
                            tokenizer.CurrentToken.Span);
                }
            }
            else if(tokenizer.CurrentToken.Kind == TokenKind.ShortComment)
            {
                return SkipComment();
            }
            else
            {
                return ParseAssignment();
            }
        }

        private Statement ParseMat()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();
            VariableOrCall matName = ParseVariableOrCall();
            if (!tokenizer.IsOperator(Operators.EqualId))
                throw SyntaxError(Properties.Resources.EqualNotFoundException,
                    tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            if (!tokenizer.IsKeyword(Keywords.ZerKeywordId))
                throw SyntaxError(Properties.Resources.ZerNotFoundException,
                    tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            SourceLocation end = tokenizer.CurrentToken.Span.Start;

            if (matName.ArgumentsCount > 0)
                throw SyntaxError(Properties.Resources.InvalidArrayNameException,
                    matName.SourceSpan);
            
            return new Mat(new SourceSpan(start, end), matName);
        }

        private Statement ParseLineInput()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();
            if (tokenizer.CurrentToken.Kind != TokenKind.Keyword ||
                tokenizer.CurrentToken.ItemId != Keywords.InputKeywordId)
            {
                throw SyntaxError(Properties.Resources.LineInputNotFoundException,
                    tokenizer.CurrentToken.Span);
            }
            tokenizer.NextToken();

            DlrBasicExpression prompt = null;
            if (tokenizer.CurrentToken.Kind == TokenKind.StringConstant)
            {
                prompt = ParseExpressionItem();
                if (tokenizer.CurrentToken.Kind == TokenKind.Semicolon)
                {
                    tokenizer.NextToken();
                }
            }

            VariableOrCall variable = ParseVariableOrCall();
            SourceLocation end = tokenizer.CurrentToken.Span.End;

            return new LineInput(new SourceSpan(start, end),
                prompt, variable);
        }

        private Statement ParseOn()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            DlrBasicExpression expression = ParseExpression();
            if (tokenizer.CurrentToken.Kind != TokenKind.Keyword ||
                (tokenizer.CurrentToken.ItemId != Keywords.GotoKeywordId &&
                 tokenizer.CurrentToken.ItemId != Keywords.GosubKeywordId))
            {
                throw SyntaxError(Properties.Resources.GotoOrGosubNotFoundException,
                    tokenizer.CurrentToken.Span);
            }
            bool isGosub = tokenizer.IsKeyword(Keywords.GosubKeywordId);
            tokenizer.NextToken();

            LineNumberTarget[] targets = ParseLineNumbers();

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new On(new SourceSpan(start, end),
                expression, isGosub, targets);
        }

        private LineNumberTarget[] ParseLineNumbers()
        {
            List<LineNumberTarget> targets = new List<LineNumberTarget>();
            targets.Add(ParseLineNumberTarget());
            while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
            {
                tokenizer.NextToken();
                targets.Add(ParseLineNumberTarget());
            }

            return targets.ToArray();
        }

        private Statement ParseDef()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            VariableOrCall leftSide = ParseVariableOrCall();
            if (leftSide.ArgumentsCount == 0 || !leftSide.Name.StartsWith("FN", StringComparison.InvariantCultureIgnoreCase))
            {
                throw SyntaxError(Properties.Resources.FunctionNameStartWithFNException,
                    leftSide.SourceSpan);
            }

            if (!tokenizer.IsOperator(Operators.EqualId))
                throw SyntaxError(Properties.Resources.EqualNotFoundException,
                    tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            DlrBasicExpression rightSide = ParseExpression();

            return new Def(
                new SourceSpan(start, rightSide.SourceSpan.End), leftSide, rightSide);
        }

        private Statement ParseRandomize()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();
            DlrBasicExpression expression = null;
            if (IsExpressionStart())
            {
                expression = ParseExpression();
            }
            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Randomize(new SourceSpan(start, end), expression);
        }

        private Statement ParseInput()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            DlrBasicExpression prompt = null;
            if (tokenizer.CurrentToken.Kind == TokenKind.StringConstant)
            {
                prompt = ParseExpressionItem();
                if (tokenizer.CurrentToken.Kind == TokenKind.Semicolon)
                {
                    tokenizer.NextToken();
                }
            }

            List<VariableOrCall> variables = new List<VariableOrCall>();
            variables.Add(ParseVariableOrCall());
            while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
            {
                tokenizer.NextToken();
                variables.Add(ParseVariableOrCall());
            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Input(new SourceSpan(start, end), prompt, variables.ToArray());
        }

        private Statement SkipComment()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.SkipToNewLine();
            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Rem(new SourceSpan(start, end), String.Empty);
        }

        private Statement ParseLet()
        {
            tokenizer.NextToken();
            return ParseAssignment();
        }

        private Statement ParseGoto()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            if (tokenizer.CurrentToken.Kind == TokenKind.NumericConstant)
            {
                LineNumberTarget target = ParseLineNumberTarget();

                SourceLocation end = tokenizer.CurrentToken.Span.Start;
                return new Goto(new SourceSpan(start, end), target);
            }
            else
            {
                DlrBasicExpression expression = ParseExpression();

                if (!tokenizer.IsKeyword(Keywords.OfKeywordId))
                    throw SyntaxError(Properties.Resources.OfNotFoundException,
                        tokenizer.CurrentToken.Span);
                tokenizer.NextToken();

                LineNumberTarget[] targets = ParseLineNumbers();

                SourceLocation end = tokenizer.CurrentToken.Span.Start;
                return new On(new SourceSpan(start, end), expression,
                    false, targets);
            }
        }

        private Statement ParseGosub()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            LineNumberTarget target = ParseLineNumberTarget();

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Gosub(new SourceSpan(start, end), target);
        }

        private Statement ParseReturn()
        {
            SourceSpan span = tokenizer.CurrentToken.Span;
            tokenizer.NextToken();
            return new Return(span);
        }

        private Statement ParseFor()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            VariableOrCall variable = ParseVariableOrCall();
            if(!tokenizer.IsOperator(Operators.EqualId))
            {
                throw SyntaxError(Properties.Resources.EqualNotFoundException,
                    tokenizer.CurrentToken.Span);
            }
            tokenizer.NextToken();
            DlrBasicExpression init = ParseExpression();

            if(!tokenizer.IsKeyword(Keywords.ToKeywordId))
            {
                throw SyntaxError(Properties.Resources.ToNotFoundException,
                    tokenizer.CurrentToken.Span);
            }
            tokenizer.NextToken();
            DlrBasicExpression bound = ParseExpression();

            DlrBasicExpression step = null;
            if (tokenizer.IsKeyword(Keywords.StepKeywordId))
            {
                tokenizer.NextToken();

                step = ParseExpression();
            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new For(new SourceSpan(start, end),
                variable, init, bound, step);
        }

        private Statement ParseNext()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            VariableOrCall[] variables = null;
            if (IsExpressionStart())
            {
                List<VariableOrCall> list = new List<VariableOrCall>();
                list.Add(ParseVariableOrCall());
                while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
                {
                    tokenizer.NextToken();
                    list.Add(ParseVariableOrCall());
                }
                variables = list.ToArray();
            }
            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Next(new SourceSpan(start, end), variables);
        }

        private Statement ParseEnd()
        {
            SourceSpan span = tokenizer.CurrentToken.Span;
            tokenizer.NextToken();
            return new End(span);
        }

        private DlrBasicExpression ParseExpression()
        {
            return ParseLogicalOperators();
        }

        private DlrBasicExpression ParseLogicalOperators()
        {
            List<DlrBasicExpression> expressions = new List<DlrBasicExpression>();
            List<int> operators = new List<int>();
            expressions.Add(ParseNotItem());
            while (true)
            {
                if (tokenizer.IsKeyword(Keywords.AndKeywordId, Keywords.OrKeywordId,
                    Keywords.XorKeywordId, Keywords.EqvKeywordId, Keywords.ImpKeywordId))
                {
                    operators.Add(tokenizer.CurrentToken.ItemId);
                    tokenizer.NextToken();
                }
                else
                    break;

                expressions.Add(ParseNotItem());
            }

            return new LogicalOperators(
                new SourceSpan(expressions[0].SourceSpan.Start, expressions[expressions.Count - 1].SourceSpan.End),
                expressions.ToArray(), operators.ToArray());
        }

        private DlrBasicExpression ParseNotItem()
        {
            if (tokenizer.CurrentToken.Kind == TokenKind.Keyword)
            {
                if (tokenizer.CurrentToken.ItemId == Keywords.NotKeywordId)
                {
                    SourceSpan span = tokenizer.CurrentToken.Span;
                    tokenizer.NextToken();
                    return new NotOperator(span, ParseRelationalOperators());

                }
            }
            return ParseRelationalOperators();
        }

        private DlrBasicExpression ParseRelationalOperators()
        {
            List<DlrBasicExpression> expressions = new List<DlrBasicExpression>();
            List<int> operators = new List<int>();
            expressions.Add(ParseArithmeticOperators());
            while (true)
            {
                if (tokenizer.IsOperator(Operators.EqualId, Operators.NotEqualId,
                    Operators.LessThenId, Operators.GreaterThenId, Operators.LessThenOrEqualId, Operators.GreaterThenOrEqualId))
                {
                    operators.Add(tokenizer.CurrentToken.ItemId);
                    tokenizer.NextToken();
                }
                else
                    break;

                expressions.Add(ParseArithmeticOperators());
            }

            return new RelationalOperators(
                new SourceSpan(expressions[0].SourceSpan.Start, expressions[expressions.Count - 1].SourceSpan.End),
                expressions.ToArray(), operators.ToArray());
        }

        private DlrBasicExpression ParseArithmeticOperators()
        {
            List<DlrBasicExpression> expressions = new List<DlrBasicExpression>();
            List<int> operators = new List<int>();
            expressions.Add(ParseNegateItem());
            while (true)
            {
                if (tokenizer.IsOperator(Operators.AddId, Operators.MultiplyId, Operators.PowerId,
                    Operators.SubstractId, Operators.DivideId, Operators.IntDivideId))
                {
                    operators.Add(tokenizer.CurrentToken.ItemId);
                    tokenizer.NextToken();
                }
                else if (tokenizer.IsKeyword(Keywords.ModKeywordId))
                {
                    operators.Add(tokenizer.CurrentToken.ItemId);
                    tokenizer.NextToken();
                }
                else
                    break;

                expressions.Add(ParseNegateItem());
            }

            return new ArithmeticOperators(
                new SourceSpan(expressions[0].SourceSpan.Start, expressions[expressions.Count - 1].SourceSpan.End),
                expressions.ToArray(), operators.ToArray());
        }

        private DlrBasicExpression ParseArithmeticPowerOperators()
        {
            List<DlrBasicExpression> expressions = new List<DlrBasicExpression>();
            List<int> operators = new List<int>();
            expressions.Add(ParseExpressionItem());
            while (true)
            {
                if (tokenizer.IsOperator(Operators.PowerId))
                {
                    operators.Add(tokenizer.CurrentToken.ItemId);
                    tokenizer.NextToken();
                }
                else
                    break;

                expressions.Add(ParseExpressionItem());
            }

            return new ArithmeticPowerOperators(
                new SourceSpan(expressions[0].SourceSpan.Start, expressions[expressions.Count - 1].SourceSpan.End),
                expressions.ToArray(), operators.ToArray());
        }

        private DlrBasicExpression ParseNegateItem()
        {
            if (tokenizer.IsOperator(Operators.SubstractId))
            {
                SourceSpan span = tokenizer.CurrentToken.Span;
                tokenizer.NextToken();
                return new NegateOperator(span, ParseArithmeticPowerOperators());
            }
            return ParseArithmeticPowerOperators();
        }

        private DlrBasicExpression ParseExpressionItem()
        {
            DlrBasicExpression result;
            switch (tokenizer.CurrentToken.Kind)
            {
                case TokenKind.NumericConstant:
                    result = ParseNumericConstant();
                    break;
                case TokenKind.BasedInteger:
                    result = ParseBasedInteger();
                    break;
                case TokenKind.StringConstant:
                    result = ParseStringConstant();
                    tokenizer.NextToken();
                    break;
                case TokenKind.LeftParenthesis:
                    result = ParseParenthesis();
                    break;
                case TokenKind.Identifier:
                case TokenKind.Keyword:
                    result = ParseVariableOrCall();
                    break;
                default:
                    throw SyntaxError(Properties.Resources.OperandNotFoundException,
                        tokenizer.CurrentToken.Span);                    
            }

            return result;
        }

        private DlrBasicExpression ParseStringConstant()
        {
            DlrBasicExpression result;
            result = new Constant(tokenizer.CurrentToken.Span,
                tokenizer.CurrentToken.Text.Replace("\"", ""), typeof(String));
            return result;
        }

        private DlrBasicExpression ParseBasedInteger()
        {
            string numeric = tokenizer.CurrentToken.Text;
            int value;
            if (Char.ToUpperInvariant(numeric[1]) == 'H')
            {
                value = Int32.Parse(numeric.Substring(2), System.Globalization.NumberStyles.HexNumber);
            }
            else
            {
                int i = Char.ToUpperInvariant(numeric[1]) == 'O' ? 2 : 1;
                value = 0;
                while (i < numeric.Length)
                {
                    value = value * 8 - (numeric[i] - '0');
                }
            }
            DlrBasicExpression result = new Constant(tokenizer.CurrentToken.Span, (Int16)value, typeof(Int16));
            tokenizer.NextToken();
            return result;
        }

        private DlrBasicExpression ParseNumericConstant()
        {
            Token primaryToken = tokenizer.CurrentToken;
            tokenizer.NextToken();
            bool hasSignature = tokenizer.CurrentToken.Kind == TokenKind.TypeSignature &&
                (tokenizer.CurrentToken.ItemId == Operators.SingleSignatureId || tokenizer.CurrentToken.ItemId == Operators.DoubleSignatureId);

            bool isDouble = false;
            SourceSpan span = primaryToken.Span;
            string numeric = primaryToken.Text.ToUpperInvariant();

            if (numeric.IndexOf('D') >= 0)
            {
                numeric = numeric.Replace('D', 'E');
                isDouble = true;
            }
            else if (numeric.Length > 7)
            {
                int digitsCount = 0;
                for (int i = 0; i < numeric.Length; i++)
                {
                    if (Char.IsDigit(numeric, i))
                        digitsCount++;
                    else if (numeric[i] == 'E')
                        break;
                }
                if (digitsCount > 7) isDouble = true;
            }
            
            if (hasSignature)
            {
                isDouble = tokenizer.CurrentToken.ItemId == Operators.DoubleSignatureId;
                span = new SourceSpan(primaryToken.Span.Start, tokenizer.CurrentToken.Span.End);
                tokenizer.NextToken();
            }

            if (isDouble)
                return new Constant(span, Double.Parse(numeric), typeof(Double));
            else
                return new Constant(span, Single.Parse(numeric), typeof(Single));
        }

        private DlrBasicExpression ParseParenthesis()
        {
            tokenizer.NextToken();
            DlrBasicExpression result = ParseExpression();
            if (tokenizer.CurrentToken.Kind != TokenKind.RightParenthesis)
                throw SyntaxError(Properties.Resources.CloseParentesisNotFoundException,
                    tokenizer.CurrentToken.Span);
            tokenizer.NextToken();

            return result;
        }

        private VariableOrCall ParseVariableOrCall()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            string name = tokenizer.CurrentToken.Text;
            tokenizer.NextToken();
            if (tokenizer.CurrentToken.Kind == TokenKind.TypeSignature)
            {
                name += tokenizer.CurrentToken.Text;
                tokenizer.NextToken();
            }
            name = name.ToUpperInvariant();

            List<DlrBasicExpression> arguments = new List<DlrBasicExpression>();
            if (tokenizer.CurrentToken.Kind == TokenKind.LeftParenthesis)
            {
                tokenizer.NextToken();

                arguments.Add(ParseExpression());
                while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
                {
                    tokenizer.NextToken();
                    arguments.Add(ParseExpression());
                }

                if (tokenizer.CurrentToken.Kind != TokenKind.RightParenthesis)
                    throw SyntaxError(Properties.Resources.CloseParentesisNotFoundException,
                        tokenizer.CurrentToken.Span);
                
                tokenizer.NextToken();
            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;

            return new VariableOrCall(new SourceSpan(start, end),
                name, arguments.ToArray());
        }

        private Statement ParseIf()
        {
            SourceLocation startLocation = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();
            DlrBasicExpression binaryExpression = ParseExpression();

            if (tokenizer.CurrentToken.Kind != TokenKind.Keyword ||
                (tokenizer.CurrentToken.ItemId != Keywords.ThenKeywordId &&
                tokenizer.CurrentToken.ItemId != Keywords.GotoKeywordId))
                throw SyntaxError(Properties.Resources.ThenOrGotoNotFoundException,
                    tokenizer.CurrentToken.Span);

            bool isGoto = tokenizer.CurrentToken.ItemId == Keywords.GotoKeywordId;
            tokenizer.NextToken();

            Statement[] thenStatements = null;

            if (tokenizer.CurrentToken.Kind == TokenKind.NumericConstant)
            {
                LineNumberTarget thenLineTarget = ParseLineNumberTarget();
                thenStatements = new Statement[] {
                        new Goto(thenLineTarget.SourceSpan, thenLineTarget)
                    };
            }
            else
            {
                if (isGoto) throw SyntaxError(Properties.Resources.LineNumberNotFoundException,
                    tokenizer.CurrentToken.Span);

                thenStatements = ParseStatements();
            }

            Statement[] elseStatements = null;
            if (tokenizer.CurrentToken.Kind == TokenKind.Keyword &&
                tokenizer.CurrentToken.ItemId == Keywords.ElseKeywordId)
            {
                tokenizer.NextToken();

                if (tokenizer.CurrentToken.Kind == TokenKind.NumericConstant)
                {
                    LineNumberTarget elseLineTarget = ParseLineNumberTarget();
                    elseStatements = new Statement[] {
                        new Goto(elseLineTarget.SourceSpan, elseLineTarget)
                    };
                }
                else
                {
                    elseStatements = ParseStatements();
                }
            }

            SourceLocation endLocation = tokenizer.CurrentToken.Span.Start;
            return new If(new SourceSpan(startLocation, endLocation),
                binaryExpression, thenStatements, elseStatements);
        }

        private LineNumberTarget ParseLineNumberTarget()
        {
            SourceSpan span = tokenizer.CurrentToken.Span;
            int lineNumber = ParseLineNumber();
            return new LineNumberTarget(span, lineNumber);
        }

        private int ParseLineNumber()
        {
            short lineNumber;
            if (!Int16.TryParse(tokenizer.CurrentToken.Text, out lineNumber))
                throw SyntaxError(Properties.Resources.InvalidLineNumberException,
                    tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            return lineNumber;
        }

        private Statement ParseDim()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            List<VariableOrCall> definitions = new List<VariableOrCall>();
            definitions.Add(ParseVariableOrCall());
            while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
            {
                tokenizer.NextToken();
                definitions.Add(ParseVariableOrCall());
            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Dim(new SourceSpan(start, end), definitions.ToArray());
        }

        private bool IsExpressionStart()
        {
            return tokenizer.CurrentToken.Kind == TokenKind.Identifier ||
                (tokenizer.CurrentToken.Kind == TokenKind.Keyword && tokenizer.CurrentToken.ItemId != Keywords.ElseKeywordId) ||
                tokenizer.CurrentToken.Kind == TokenKind.BasedInteger ||
                tokenizer.CurrentToken.Kind == TokenKind.StringConstant ||
                tokenizer.CurrentToken.Kind == TokenKind.NumericConstant ||
                tokenizer.CurrentToken.Kind == TokenKind.LeftParenthesis;
        }

        private Statement ParsePrint()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            DlrBasicExpression format = null;
            bool preserveTabs = true;

            if (tokenizer.IsKeyword(Keywords.UsingKeywordId))
            {
                tokenizer.NextToken();
                format = ParseExpression();
                preserveTabs = false;
            }

            bool newLine = true;
            List<DlrBasicExpression> expressions = new List<DlrBasicExpression>();
            while (tokenizer.CurrentToken.Kind ==  TokenKind.Semicolon ||
                tokenizer.CurrentToken.Kind == TokenKind.Colon ||
                IsExpressionStart())
            {
                if (tokenizer.CurrentToken.Kind == TokenKind.Semicolon)
                {
                    newLine = false;
                    tokenizer.NextToken();
                }
                else if (tokenizer.CurrentToken.Kind == TokenKind.Colon)
                {
                    if (preserveTabs)
                    {
                        expressions.Add(null);
                    }
                    tokenizer.NextToken();                    
                }
                else
                {
                    expressions.Add(ParseExpression());
                    newLine = true;
                }
            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Print(new SourceSpan(start, end), expressions.ToArray(), newLine, format);
        }

        private Statement ParseCls()
        {
            Cls cls = new Cls(tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            return cls;
        }

        private Statement ParseBeep()
        {
            Beep beep = new Beep(tokenizer.CurrentToken.Span);
            tokenizer.NextToken();
            return beep;
        }

        private Statement ParseClear()
        {
            SourceLocation start = tokenizer.CurrentToken.Span.Start;
            tokenizer.NextToken();

            DlrBasicExpression memory = null;
            DlrBasicExpression stack = null;

            if (tokenizer.CurrentToken.Kind != TokenKind.NewLine &&
                tokenizer.CurrentToken.Kind != TokenKind.NewStatement &&
                tokenizer.CurrentToken.Kind != TokenKind.EndOfFile)
            {


                if (tokenizer.CurrentToken.Kind == TokenKind.Colon)
                {
                    tokenizer.NextToken();

                    if (tokenizer.CurrentToken.Kind != TokenKind.Colon)
                    {
                        memory = ParseExpression();
                    }

                    if (tokenizer.CurrentToken.Kind == TokenKind.Colon)
                    {
                        tokenizer.NextToken();
                        stack = ParseExpression();
                    }
                }
                else
                {
                    memory = ParseExpression();
                }

            }

            SourceLocation end = tokenizer.CurrentToken.Span.Start;
            return new Clear(new SourceSpan(start, end), memory, stack);
        }

        private Statement ParseAssignment()
        {
            List<VariableOrCall> leftSide = new List<VariableOrCall>();
            leftSide.Add(ParseVariableOrCall());
            while (tokenizer.CurrentToken.Kind == TokenKind.Colon)
            {
                tokenizer.NextToken();
                leftSide.Add(ParseVariableOrCall());
            }

            if (!tokenizer.IsOperator(Operators.EqualId)) 
                throw SyntaxError(Properties.Resources.EqualNotFoundException, 
                    tokenizer.CurrentToken.Span);

            tokenizer.NextToken();
            DlrBasicExpression rightSide = ParseExpression();

            return new Assign(
                new SourceSpan(leftSide[0].SourceSpan.Start, rightSide.SourceSpan.End),
                leftSide.ToArray(), rightSide);
        }

        internal Exception SyntaxError(string message, SourceSpan span)
        {
            return new SyntaxErrorException(message, source, span, 2, Severity.Error);
        }

        internal InteractiveLine ParseInteractiveStatement()
        {
            if (tokenizer.CurrentToken.Kind != TokenKind.NewLine &&
                tokenizer.CurrentToken.Kind != TokenKind.EndOfFile)
            {
                Statement[] statements = ParseStatements();
                return new InteractiveLine(new SourceSpan(statements[0].SourceSpan.Start, statements[statements.Length - 1].SourceSpan.End),
                    statements);
            }
            else
            {
                return new InteractiveLine(SourceSpan.None, new Statement[0]);
            }
        }
    }
}
